package proposallineage

import (
	"fmt"
	"strings"

	"github.com/git-town/git-town/v22/internal/config/configdomain"
	"github.com/git-town/git-town/v22/internal/forge/forgedomain"
	"github.com/git-town/git-town/v22/internal/git/gitdomain"
	. "github.com/git-town/git-town/v22/pkg/prelude"
)

const indentMarker = "-"

type Builder struct {
	mainAndPerennialBranches gitdomain.LocalBranchNames
	tree                     *Tree
}

type ProposalStackLineageArgs struct {
	Connector                Option[forgedomain.ProposalFinder]
	CurrentBranch            gitdomain.LocalBranchName
	Lineage                  configdomain.Lineage
	MainAndPerennialBranches gitdomain.LocalBranchNames
	Order                    configdomain.Order
}

func NewBuilder(args ProposalStackLineageArgs, lineageTree OptionalMutable[Tree]) Option[Builder] {
	if args.MainAndPerennialBranches.Contains(args.CurrentBranch) ||
		args.Lineage.Len() == 0 {
		// cannot create proposal stack lineage for main or perennial branch
		return None[Builder]()
	}

	tree, hasTree := lineageTree.Get()
	if !hasTree {
		var err error
		tree, err = NewTree(args)
		if err != nil {
			fmt.Printf("failed to build proposal stack lineage: %s", err.Error())
			return None[Builder]()
		}
	}

	if tree == nil {
		return None[Builder]()
	}

	builder := &Builder{
		mainAndPerennialBranches: args.MainAndPerennialBranches,
		tree:                     tree,
	}

	return Some(*builder)
}

// Build returns the proposal stack lineage as a string
func (self *Builder) Build(args ProposalStackLineageArgs) string {
	var builder strings.Builder
	builder.WriteString("\n-------------------------\n")
	builder.WriteString(self.build(self.tree.Node, args, 0))
	builder.WriteString("\n<sup>[Stack](https://www.git-town.com/how-to/github-actions-breadcrumb.html) generated by [Git Town](https://github.com/git-town/git-town)</sup>\n")
	return builder.String()
}

// UpdateStack updates the underlying tree representation of the proposal stack. If proposal data was
// fetched on a previous call to UpdateStack or during ProposalStackLineageBuilder, the underlying
// data structure fetches that information from a map.
func (self *Builder) UpdateStack(args ProposalStackLineageArgs) error {
	return self.tree.Rebuild(args)
}

func (self *Builder) build(node *TreeNode, args ProposalStackLineageArgs, indentLevel int) string {
	var builder strings.Builder
	indent := strings.Repeat(" ", indentLevel*2)
	nextIndentLevel := indentLevel + 1
	if args.MainAndPerennialBranches.Contains(node.branch) {
		builder.WriteString(fmt.Sprintf("%s %s %s\n", indent, indentMarker, node.branch.BranchName()))
		for _, child := range node.childNodes {
			builder.WriteString(self.build(child, args, nextIndentLevel))
		}
		return builder.String()
	}

	proposal, hasProposal := node.proposal.Get()
	if hasProposal {
		builder.WriteString(formattedDisplay(args, indent, proposal))
	} else {
		nextIndentLevel--
	}

	for _, child := range node.childNodes {
		builder.WriteString(self.build(child, args, nextIndentLevel))
	}

	return builder.String()
}

func formattedDisplay(args ProposalStackLineageArgs, currentIndentLevel string, proposal forgedomain.Proposal) string {
	proposalData := proposal.Data
	if args.CurrentBranch == proposalData.Data().Source {
		return fmt.Sprintf("%s %s %s :point_left:\n", currentIndentLevel, indentMarker, proposalData.Data().URL)
	}
	return fmt.Sprintf("%s %s %s\n", currentIndentLevel, indentMarker, proposalData.Data().URL)
}
